﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using System.Reflection;
using System.IO;
using System.CodeDom.Compiler;
using System.Diagnostics;
using Microsoft.ServiceModel.DomainServices.Tools.TextTemplate;
using Microsoft.ServiceModel.DomainServices.Tools.TextTemplate.CSharpGenerators;
using CodeGenerationPipeline.Contracts;
using System.Collections;
using AbstraX.Contracts;
using Utils;
using Metaspec;
using AbstraX;

namespace CodeGenerationPipeline
{
    /// <summary>
    /// </summary>
    public class PipelineTemplateEngineHost : IPipelineTemplateEngineHost
    {
        private Type generatorType;
        private Stack<IGenerator> generatorStack;
        public EmitDebugInfoLevel EmitDebugInfoLevel { get; set; }
        public event EventHandler DebugCallback;
        public event AbortStepHandler AbortBuild;
        public event DefineConstructHandler DefineConstruct;
        public event DefineTypeHandler DefineType;
        public event GetBuildNameHandler GetBuildName;
        public event GetElementNameHandler GetElementName;
        public event GetTypeNameHandler GetTypeName;
        public event GetBuildNameHandler GetReferencedBuildName;
        public event GetElementNameHandler GetReferencedElementName;
        public event GetTypeNameHandler GetReferencedTypeName;
        public event GetNavigationPropertyNameHandler GetNavigationPropertyName;
        public event GetPropertyBindingPropertyNameHandler GetPropertyBindingPropertyName;
        public event GetMethodOperationNameHandler GetMethodOperationName;
        public event GetAttributeNameHandler GetAttributeName;
        public event ElementIsToBeBuiltHandler ElementIsToBeBuilt;
        public event TypeIsToBeBuiltHandler TypeIsToBeBuilt;
        public event OnGeneratorFileWithBuildPreBuildHandler OnGeneratorFileWithBuildPreBuild;
        public event OnGeneratorFileWithListPreBuildHandler OnGeneratorFileWithListPreBuild;
        public event OnGeneratorFileWithBuildPostBuildHandler OnGeneratorFileWithBuildPostBuild;
        public event OnGeneratorFileWithListPostBuildHandler OnGeneratorFileWithListPostBuild;
        public event OnGeneratorFileWithBuildCodeWriterHandler OnGeneratorFileWithBuildCodeWriter;
        public event OnGeneratorFileWithListCodeWriterHandler OnGeneratorFileWithListCodeWriter;

        public Stack<IGenerator> GeneratorStack
        {
            get 
            {
                return generatorStack; 
            }
        }

        public PipelineTemplateEngineHost()
        {
            generatorStack = new Stack<IGenerator>();
        }

        public PipelineTemplateEngineHost(Type generatorType)
        {
            this.generatorType = generatorType;
        }

        public void GenerateFile<TGeneratorType>(IElementBuild build, FileInfo fileInfo, string _namespace, Dictionary<string, object> extraValues = null)
        {
            this.generatorType = typeof(TGeneratorType);

            this.GenerateFile(build, fileInfo, _namespace, extraValues);
        }

        public void GenerateFile(IElementBuild build, FileInfo fileInfo, string _namespace, Dictionary<string, object> extraValues = null)
        {
            try
            {
                var skipBuildFile = false;
                var abortBuild = false;

                OnGeneratorFileWithBuildPreBuild(ref generatorType, ref build, ref fileInfo, ref _namespace, ref extraValues, out skipBuildFile, out abortBuild);

                if (fileInfo.Exists)
                {
                    if (fileInfo.IsReadOnly)
                    {
                        fileInfo.MakeWritable();
                    }

                    fileInfo.Delete();
                }

                if (abortBuild)
                {
                    AbortBuild(this, new StepAbortionEventArgs(fileInfo));
                    return;
                }

                if (!skipBuildFile)
                {
                    dynamic codeGenerator = Activator.CreateInstance(generatorType);
                    var session = new TextTemplatingSession();

                    session["ElementBuild"] = build;
                    session["NamespaceName"] = _namespace;
                    session["EmitDebugInfoLevel"] = EmitDebugInfoLevel;

                    if (extraValues != null)
                    {
                        session["ExtraValues"] = extraValues;
                    }

                    if (DebugCallback != null)
                    {
                        session["DebugCallback"] = DebugCallback;
                    }

                    if (DefineConstruct != null)
                    {
                        session["DefineConstruct"] = DefineConstruct;
                    }

                    if (DefineType != null)
                    {
                        session["DefineType"] = DefineType;
                    }

                    if (ElementIsToBeBuilt != null)
                    {
                        session["ElementIsToBeBuilt"] = ElementIsToBeBuilt;
                    }

                    if (TypeIsToBeBuilt != null)
                    {
                        session["TypeIsToBeBuilt"] = TypeIsToBeBuilt;
                    }

                    if (GetBuildName != null)
                    {
                        session["GetBuildName"] = GetBuildName;
                    }

                    if (GetElementName != null)
                    {
                        session["GetElementName"] = GetElementName;
                    }

                    if (GetTypeName != null)
                    {
                        session["GetTypeName"] = GetTypeName;
                    }

                    if (GetReferencedBuildName != null)
                    {
                        session["GetReferencedBuildName"] = GetReferencedBuildName;
                    }

                    if (GetReferencedElementName != null)
                    {
                        session["GetReferencedElementName"] = GetReferencedElementName;
                    }

                    if (GetReferencedTypeName != null)
                    {
                        session["GetReferencedTypeName"] = GetReferencedTypeName;
                    }

                    if (GetNavigationPropertyName != null)
                    {
                        session["GetNavigationPropertyName"] = GetNavigationPropertyName;
                    }

                    if (GetPropertyBindingPropertyName != null)
                    {
                        session["GetPropertyBindingPropertyName"] = GetPropertyBindingPropertyName;
                    }

                    if (GetMethodOperationName != null)
                    {
                        session["GetMethodOperationName"] = GetMethodOperationName;
                    }

                    if (GetAttributeName != null)
                    {
                        session["GetAttributeName"] = GetAttributeName;
                    }

                    codeGenerator.Session = session;
                    codeGenerator.Initialize();

                    using (var writer = new CodeWriter(fileInfo, this.EmitDebugInfoLevel))
                    {
                        var iWriter = (ICodeWriter) writer;

                        OnGeneratorFileWithBuildCodeWriter(build, fileInfo, _namespace, extraValues, ref iWriter);

                        var output = (string)codeGenerator.TransformText();

                        var generator = generatorStack.Peek();
                        var codeParser = generator.ProgrammingLanguage.CodeParser;

                        iWriter.Write(output);

                        if (iWriter != writer)
                        {
                            iWriter.Dispose();
                        }

                        OnGeneratorFileWithBuildPostBuild(build, ref fileInfo, _namespace, extraValues, codeParser, ref output);
                    }
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        public void GenerateFile<TGeneratorType>(FileInfo fileInfo, string _namespace, Dictionary<string, object> values = null)
        {
            this.generatorType = typeof(TGeneratorType);

            this.GenerateFile(fileInfo, _namespace, values);
        }

        public void GenerateFile(FileInfo fileInfo, string _namespace, Dictionary<string, object> values = null)
        {
            try
            {
                var skipBuildFile = false;
                var abortBuild = false;
                var list = (IEnumerable)values;

                OnGeneratorFileWithListPreBuild(ref generatorType, ref fileInfo, ref _namespace, ref list, out skipBuildFile, out abortBuild);

                values = (Dictionary<string, object>)list;

                if (abortBuild)
                {
                    AbortBuild(this, new StepAbortionEventArgs(fileInfo));
                    return;
                }

                if (!skipBuildFile)
                {
                    dynamic codeGenerator = Activator.CreateInstance(generatorType);
                    var session = new TextTemplatingSession();

                    session["Name"] = Path.GetFileNameWithoutExtension(fileInfo.Name);
                    session["Namespace"] = _namespace;
                    session["EmitDebugInfoLevel"] = EmitDebugInfoLevel;

                    if (values != null)
                    {
                        session["Values"] = values;
                    }

                    codeGenerator.Session = session;
                    codeGenerator.Initialize();

                    using (var writer = new CodeWriter(fileInfo, this.EmitDebugInfoLevel))
                    {
                        var iWriter = (ICodeWriter)writer;

                        OnGeneratorFileWithListCodeWriter(fileInfo, _namespace, list, ref iWriter);

                        var output = (string)codeGenerator.TransformText();

                        var generator = generatorStack.Peek();
                        var codeParser = generator.ProgrammingLanguage.CodeParser;

                        iWriter.Write(output);

                        if (iWriter != writer)
                        {
                            iWriter.Dispose();
                        }

                        OnGeneratorFileWithListPostBuild(ref fileInfo, _namespace, list, codeParser, ref output);
                    }
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        public void GenerateFile<TGeneratorType>(FileInfo fileInfo, string _namespace, IEnumerable list = null)
        {
            this.generatorType = typeof(TGeneratorType);

            this.GenerateFile(fileInfo, _namespace, list);
        }

        public void GenerateFile(FileInfo fileInfo, string _namespace, IEnumerable list = null)
        {
            try
            {
                var skipBuildFile = false;
                var abortBuild = false;

                OnGeneratorFileWithListPreBuild(ref generatorType, ref fileInfo, ref _namespace, ref list, out skipBuildFile, out abortBuild);

                if (abortBuild)
                {
                    AbortBuild(this, new StepAbortionEventArgs(fileInfo));
                    return;
                }

                if (!skipBuildFile)
                {
                    dynamic codeGenerator = Activator.CreateInstance(generatorType);
                    var session = new TextTemplatingSession();

                    session["Namespace"] = _namespace;
                    session["Name"] = Path.GetFileNameWithoutExtension(fileInfo.Name);
                    session["EmitDebugInfoLevel"] = EmitDebugInfoLevel;

                    if (list != null)
                    {
                        session["List"] = list;
                    }

                    codeGenerator.Session = session;
                    codeGenerator.Initialize();

                    using (var writer = new CodeWriter(fileInfo, this.EmitDebugInfoLevel))
                    {
                        var iWriter = (ICodeWriter)writer;

                        OnGeneratorFileWithListCodeWriter(fileInfo, _namespace, list, ref iWriter);

                        var output = (string)codeGenerator.TransformText();

                        var generator = generatorStack.Peek();
                        var codeParser = generator.ProgrammingLanguage.CodeParser;

                        iWriter.Write(output);

                        if (iWriter != writer)
                        {
                            iWriter.Dispose();
                        }

                        OnGeneratorFileWithListPostBuild(ref fileInfo, _namespace, list, codeParser, ref output);
                    }
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }
    }
}
